//
//  IMService.m
//  ipjsua
//
//  Created by Andy on 16/5/18.
//  Copyright © 2016年 Teluu. All rights reserved.
//

#import "IMService.h"

#include <pjsua-lib/pjsua.h>
#import "ipjsuaAppDelegate.h"
#import "IMViewController.h"

/* Ringtones		        US	       UK   */
#define RINGBACK_FREQ1	    440	    /* 400  */
#define RINGBACK_FREQ2	    480	    /* 450  */
#define RINGBACK_ON	        2000    /* 400  */
#define RINGBACK_OFF	    4000    /* 200  */
#define RINGBACK_CNT	    1	    /* 2    */
#define RINGBACK_INTERVAL   4000    /* 2000 */

#define DEFAULT_UDP_PORT 5060
#define DEFAULT_RTP_PORT 4000
#define DEFAULT_LOG_LEVEL 7

#define DEFAULT_NAME_LENGHT 64
#define DEFAULT_DEV_NUMBERS 64

#define FrontCamera "Front Camera"
#define BackCamera  "Back Camera"

#define current_acc	pjsua_acc_get_default()
#define THIS_FILE   "IMService.m"

/* Video settings */
typedef struct app_vid
{
    unsigned		vid_cnt;
    int			    vcapture_dev;
    int			    vrender_dev;
    pj_bool_t		in_auto_show;
    pj_bool_t		out_auto_transmit;
} app_vid;

/* Pjsua application data */
typedef struct pjsua_app_config
{
    pjsua_config	        cfg;
    pjsua_logging_config    log_cfg;
    pjsua_media_config	    media_cfg;
    pj_bool_t		        ipv6;
    pj_bool_t		        no_udp;
    pjsua_transport_config  udp_cfg;
    pjsua_transport_config  rtp_cfg;
    pjsua_acc_config	    acc_cfg[PJSUA_MAX_ACC];
    pj_pool_t		        *pool;
    unsigned		        capture_lat, playback_lat;
    pj_bool_t		        no_tones;
    pj_bool_t               ring_on;
    pj_bool_t               ringback_on;
    int			            ringback_slot;
    int			            ringback_cnt;
    pjmedia_port	        *ringback_port;
    app_vid		            vid;
    unsigned		        aud_cnt;
} pjsua_app_config;


struct opt{
    int id;
    char name[DEFAULT_NAME_LENGHT];
}opts[DEFAULT_DEV_NUMBERS];

pjsua_app_config    app_config;
pjsua_call_setting  call_opt;
pjsua_call_id	    current_call = PJSUA_INVALID_ID;

@implementation IMService

static IMService *instance = nil;

+ (IMService *)sharedInstance
{
    @synchronized (self){//为了确保多线程情况下，仍然确保实体的唯一性
        if (!instance) {
            instance = [[self alloc] init];//非ARC模式下,该方法会调用 allocWithZone
        }
        return instance;
    }
}

+ (id)allocWithZone:(NSZone *)zone
{
    @synchronized(self){
        if (!instance) {
            instance = [super allocWithZone:zone]; //确保使用同一块内存地址
        }
        return instance;
    }
}

- (id)copyWithZone:(NSZone *)zone
{
    return self; //确保copy对象也是唯一
}


#pragma mark - PJSIP -

/*设置默认配置*/
static void default_config()
{
    char tmp[80];
    unsigned i;
    pjsua_app_config *cfg = &app_config;
    
    pjsua_config_default(&cfg->cfg);
    pj_ansi_sprintf(tmp, "PJSUA v%s %s", pj_get_version(), pj_get_sys_info()->info.ptr);
    pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
    
    pjsua_logging_config_default(&cfg->log_cfg);
    pjsua_media_config_default(&cfg->media_cfg);
    pjsua_transport_config_default(&cfg->udp_cfg);
    cfg->udp_cfg.port = DEFAULT_UDP_PORT;
    pjsua_transport_config_default(&cfg->rtp_cfg);
    cfg->rtp_cfg.port = DEFAULT_RTP_PORT;
    cfg->capture_lat  = PJMEDIA_SND_DEFAULT_REC_LATENCY;
    cfg->playback_lat = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
    cfg->ringback_slot = PJSUA_INVALID_ID;
    
    for (i=0; i<PJ_ARRAY_SIZE(cfg->acc_cfg); ++i)
        pjsua_acc_config_default(&cfg->acc_cfg[i]);
    
    cfg->vid.vcapture_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
    cfg->vid.vrender_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
    cfg->aud_cnt = 1;
    cfg->vid.vid_cnt = 1;
    cfg->vid.in_auto_show = PJ_TRUE;
    cfg->vid.out_auto_transmit = PJ_TRUE;
    cfg->media_cfg.quality = 4;
    
    cfg->ipv6 = PJ_TRUE;
    
    cfg->log_cfg.level = DEFAULT_LOG_LEVEL;
    pj_log_set_level( DEFAULT_LOG_LEVEL );
    cfg->log_cfg.console_level = DEFAULT_LOG_LEVEL;
}

/* 对方接收到Invite请求，然后回180后，本段的铃声 */
static void ringback_start(pjsua_call_id call_id)
{
    if (app_config.no_tones)
        return;
    
    app_config.ringback_on = PJ_TRUE;
    if (++app_config.ringback_cnt==1 && app_config.ringback_slot!=PJSUA_INVALID_ID)
    {
        pjsua_conf_connect(app_config.ringback_slot, 0);
    }
}

/* 停止来电铃声 */
static void ring_stop(pjsua_call_id call_id)
{
    if (app_config.no_tones)
        return;
    
    //关闭回声
    if (app_config.ringback_on)
    {
        app_config.ringback_on = PJ_FALSE;
        pj_assert(app_config.ringback_cnt>0);
        if (--app_config.ringback_cnt == 0 && app_config.ringback_slot!=PJSUA_INVALID_ID)
        {
            pjsua_conf_disconnect(app_config.ringback_slot, 0);
            pjmedia_tonegen_rewind(app_config.ringback_port);
        }
    }
    
    //关闭铃声
    if(app_config.ring_on)
    {
        [[IMService sharedInstance] IM_RingStop];
    }
}


/* 来电响铃 */
static void ring_start(pjsua_call_id call_id)
{
    if (app_config.no_tones || app_config.ring_on)
        return;
    
    app_config.ring_on = PJ_TRUE;
    //开启铃声
    [[IMService sharedInstance] IM_RingStart];
}

/* Process audio media state. "mi" is the media index. */
static void on_call_audio_state(pjsua_call_info *ci, unsigned mi,
                                pj_bool_t *has_error)
{
    PJ_UNUSED_ARG(has_error);
    
    /* Stop ringback */
    ring_stop(ci->id);
    
    /*
     * Connect ports appropriately when media status is ACTIVE or REMOTE HOLD,
     * otherwise we should NOT connect the ports.
     */
    if (ci->media[mi].status == PJSUA_CALL_MEDIA_ACTIVE || ci->media[mi].status == PJSUA_CALL_MEDIA_REMOTE_HOLD)
    {
        pj_bool_t connect_sound = PJ_TRUE;
        pj_bool_t disconnect_mic = PJ_FALSE;
        pjsua_conf_port_id call_conf_slot;
        
        call_conf_slot = ci->media[mi].stream.aud.conf_slot;
        
        /* Otherwise connect to sound device */
        if (connect_sound)
        {
            pjsua_conf_connect(call_conf_slot, 0);
            if (!disconnect_mic)
                pjsua_conf_connect(0, call_conf_slot);
        }
    }
}


/* Process video media state. "mi" is the media index. */
static void on_call_video_state(pjsua_call_info *ci, unsigned mi,
                                pj_bool_t *has_error)
{
    if (ci->media_status != PJSUA_CALL_MEDIA_ACTIVE)
        return;
    arrange_window(ci->media[mi].stream.vid.win_in);
    PJ_UNUSED_ARG(has_error);
}


/*
 * 当前通话断开的时候，寻找下一个通话
 */
pj_bool_t find_next_call()
{
    int i, max;
    
    max = pjsua_call_get_max_count();
    for (i=current_call+1; i<max; ++i)
    {
        if (pjsua_call_is_active(i))
        {
            current_call = i;
            return PJ_TRUE;
        }
    }
    
    for (i=0; i<current_call; ++i)
    {
        if (pjsua_call_is_active(i))
        {
            current_call = i;
            return PJ_TRUE;
        }
    }
    
    current_call = PJSUA_INVALID_ID;
    return PJ_FALSE;
}

/* General processing for media state. "mi" is the media index */
static void on_call_generic_media_state(pjsua_call_info *ci, unsigned mi, pj_bool_t *has_error)
{
    const char *status_name[] = {
        "None",
        "Active",
        "Local hold",
        "Remote hold",
        "Error"
    };
    
    PJ_UNUSED_ARG(has_error);
    
    pj_assert(ci->media[mi].status <= PJ_ARRAY_SIZE(status_name));
    pj_assert(PJSUA_CALL_MEDIA_ERROR == 4);
    
    PJ_LOG(4,(THIS_FILE, "Call %d media %d [type=%s], status is %s",
              ci->id, mi, pjmedia_type_name(ci->media[mi].type),
              status_name[ci->media[mi].status]));
}

/*
 * Callback on media state changed event.
 * The action may connect the call to sound device, to file, or
 * to loop the call.
 */
static void on_call_media_state(pjsua_call_id call_id)
{
    pjsua_call_info call_info = {0};
    unsigned mi = -1;
    pj_bool_t has_error = PJ_FALSE;
    
    pjsua_call_get_info(call_id, &call_info);
    
    PJ_LOG(3,(THIS_FILE,"----------------date=%.*s----------",call_info.date.slen,call_info.date.ptr));
    for (mi=0; mi<call_info.media_cnt; ++mi)
    {
        on_call_generic_media_state(&call_info, mi, &has_error);
        
        switch (call_info.media[mi].type)
        {
            case PJMEDIA_TYPE_AUDIO:
                on_call_audio_state(&call_info, mi, &has_error);
                break;
            case PJMEDIA_TYPE_VIDEO:
                on_call_video_state(&call_info, mi, &has_error);
                break;
            default:
                break;
        }
    }
    
    if (has_error)
    {
        pj_str_t reason = pj_str("Media failed");
        pjsua_call_hangup(call_id, 500, &reason, NULL);
    }
    
    /* Check if remote has just tried to enable video */
    if (call_info.rem_offerer && call_info.rem_vid_cnt)
    {
        int vid_idx;
        
        /* Check if there is active video */
        vid_idx = pjsua_call_get_vid_stream_idx(call_id);
        if (vid_idx == -1 || call_info.media[vid_idx].dir == PJMEDIA_DIR_NONE)
        {
            PJ_LOG(3,(THIS_FILE,
                      "Just rejected incoming video offer on call %d, "
                      "use \"vid call enable %d\" or \"vid call add\" to "
                      "enable video!", call_id, vid_idx));
        }
    }
}



/*
 * 当Invite状态变化的时候处理
 */
static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
{
    pjsua_call_info call_info;
    
    PJ_UNUSED_ARG(e);
    
    pjsua_call_get_info(call_id, &call_info);
 
    if (call_info.state == PJSIP_INV_STATE_DISCONNECTED)
    {
        /* Stop all ringback for this call */
        ring_stop(call_id);

        PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",call_id,call_info.last_status,call_info.last_status_text.ptr));
        
        //通知UI，Invite连接断开
        [[IMService sharedInstance] IM_InviteDisconnectWithcallid:call_id code:call_info.last_status status:&call_info.last_status_text];
        
        if (call_id == current_call) {
            find_next_call();
        }
    }
    else
    {
        if (call_info.state == PJSIP_INV_STATE_EARLY)
        {
            int code;
            pj_str_t reason;
            pjsip_msg *msg;
            
            /* This can only occur because of TX or RX message */
            pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
            
            if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
            {
                msg = e->body.tsx_state.src.rdata->msg_info.msg;
            }
            else {
                msg = e->body.tsx_state.src.tdata->msg;
            }
            
            code = msg->line.status.code;
            reason = msg->line.status.reason;
            
            /* Start ringback for 180 for UAC unless there's SDP in 180 */
            if (call_info.role==PJSIP_ROLE_UAC && code==180 && msg->body == NULL && call_info.media_status==PJSUA_CALL_MEDIA_NONE)
            {
                ringback_start(call_id);
            }
            
            PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)", call_id, call_info.state_text.ptr, code, (int)reason.slen, reason.ptr));
        }
        else
        {
            PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", call_id, call_info.state_text.ptr));
        }
        
        if (current_call==PJSUA_INVALID_ID)
            current_call = call_id;
    }
}


/**
 * 电话呼入处理
 */
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata)
{
    pjsua_call_info call_info;
    
    PJ_UNUSED_ARG(acc_id);
    PJ_UNUSED_ARG(rdata);
    
    pjsua_call_get_info(call_id, &call_info);
    if (current_call==PJSUA_INVALID_ID)
        current_call = call_id;
    
    //提取call_id
    PJ_LOG(3, (THIS_FILE,"------------andy--------- CALL ----------Call_id=%.*s",call_info.call_id.slen,call_info.call_id.ptr));
    
    ring_start(call_id);
    answercall(180);
    if (call_info.rem_offerer && call_info.rem_vid_cnt)
        [[IMService sharedInstance] IM_RecvVideoCallWithfrom:&call_info.remote_info to:&call_info.local_info];
    else
        [[IMService sharedInstance] IM_RecvAudioCallWithfrom:&call_info.remote_info to:&call_info.local_info];
}

/*
 * 处理注册状态变更
 */
static void on_reg_state2(pjsua_acc_id acc_id,pjsua_reg_info *info)
{
    PJ_UNUSED_ARG(acc_id);
    
    if(info->renew == 0)
    {
        //退出成功
        [[IMService sharedInstance] IM_LogOutSuccess];
    }
    else
    {
        //注册成功
        [[IMService sharedInstance] IM_LogInSuccess];
        PJ_LOG(3,(THIS_FILE,"------andy------Call_id=%.*s",info->cbparam->rdata->msg_info.cid->id.slen,info->cbparam->rdata->msg_info.cid->id.ptr));
    }
    //没注册上的时候这里不能打印CallID
}

/*
 * 收到踢人的消息
 */
static void on_reg_state3(const pj_str_t *from, const pj_str_t *to,
                          pjsip_rx_data *rdata)
{
    PJ_UNUSED_ARG(from);
    PJ_UNUSED_ARG(to);
    PJ_UNUSED_ARG(rdata);
    PJ_LOG(3,(THIS_FILE,"------andy------"));
}


/**
 * 收到IM消息
 */
static void on_pager2(pjsua_call_id call_id, const pj_str_t *from,
                  const pj_str_t *to, const pj_str_t *contact,
                  const pj_str_t *mime_type, const pj_str_t *body,
                  pjsip_rx_data *rdata, pjsua_acc_id acc_id)
{
    PJ_UNUSED_ARG(call_id);
    PJ_UNUSED_ARG(to);
    PJ_UNUSED_ARG(contact);
    PJ_UNUSED_ARG(mime_type);
    
//    PJ_LOG(3,(THIS_FILE,"-------andy-----msg-----Call_id=%.*s---------date=%.*s-------------",rdata->msg_info.cid->id.slen,rdata->msg_info.cid->id.ptr
//              ,rdata->msg_info.date->id.slen,rdata->msg_info.date->id.ptr));
    [[IMService sharedInstance] IM_RecvMsgWithfrom:from to:to text:body];
}

static void on_pager_status2(pjsua_call_id call_id,
                                         const pj_str_t *to,
                                         const pj_str_t *body,
                                         void *user_data,
                                         pjsip_status_code status,
                                         const pj_str_t *reason,
                                         pjsip_tx_data *tdata,
                                         pjsip_rx_data *rdata,
                                         pjsua_acc_id acc_id)
{
    PJ_LOG(3,(THIS_FILE,"-------andy-----msg--reason=%.*s---Call_id=%.*s------date=%.*s-------------",reason->slen, reason->ptr,rdata->msg_info.cid->id.slen,rdata->msg_info.cid->id.ptr,rdata->msg_info.date->id.slen,rdata->msg_info.date->id.ptr));
}



/**
 * 收到正在输入提示
 */
static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
                      const pj_str_t *to, const pj_str_t *contact,
                      pj_bool_t is_typing)
{
    PJ_UNUSED_ARG(call_id);
    PJ_UNUSED_ARG(to);
    PJ_UNUSED_ARG(contact);
    
    PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s", (int)from->slen, from->ptr, (is_typing?"is typing..":"has stopped typing")));
    [[IMService sharedInstance] IM_RemoteIsTypingWithfrom:from to:to is_typing:is_typing];
}

/* Callback on media events */
static void on_call_media_event(pjsua_call_id call_id, unsigned med_idx, pjmedia_event *event)
{
    char event_name[5];
    
    PJ_LOG(5,(THIS_FILE, "Event %s", pjmedia_fourcc_name(event->type, event_name)));
 
    if (event->type == PJMEDIA_EVENT_FMT_CHANGED)
    {
        /* Adjust renderer window size to original video size */
        pjsua_call_info ci;
        pjsua_call_get_info(call_id, &ci);
        if ((ci.media[med_idx].type == PJMEDIA_TYPE_VIDEO) &&(ci.media[med_idx].dir & PJMEDIA_DIR_DECODING))
        {
            pjsua_vid_win_id wid;
            pjmedia_rect_size size;
            pjsua_vid_win_info win_info;
            
            wid = ci.media[med_idx].stream.vid.win_in;
            pjsua_vid_win_get_info(wid, &win_info);
            
            size = event->data.fmt_changed.new_fmt.det.vid.size;
            if (size.w != win_info.size.w || size.h != win_info.size.h)
            {
                pjsua_vid_win_set_size(wid, &size);
                arrange_window(PJSUA_INVALID_ID);
            }
        }
    }
}

void app_config_init_video(pjsua_acc_config *acc_cfg)
{
    acc_cfg->vid_in_auto_show = app_config.vid.in_auto_show;
    acc_cfg->vid_out_auto_transmit = app_config.vid.out_auto_transmit;

    acc_cfg->vid_wnd_flags = PJMEDIA_VID_DEV_WND_BORDER | PJMEDIA_VID_DEV_WND_RESIZABLE;
    acc_cfg->vid_cap_dev = app_config.vid.vcapture_dev;
    acc_cfg->vid_rend_dev = app_config.vid.vrender_dev;
}

static pj_status_t app_destroy()
{
    pj_status_t status = PJ_SUCCESS;
    
    /* Close ringback port */
    if (app_config.ringback_port && app_config.ringback_slot != PJSUA_INVALID_ID)
    {
        pjsua_conf_remove_port(app_config.ringback_slot);
        app_config.ringback_slot = PJSUA_INVALID_ID;
        pjmedia_port_destroy(app_config.ringback_port);
        app_config.ringback_port = NULL;
    }
    
    if (app_config.pool) {
        pj_pool_release(app_config.pool);
        app_config.pool = NULL;
    }
    
    status = pjsua_destroy();
    
    /* Reset config */
    pj_bzero(&app_config, sizeof(app_config));
    
    return status;
}


static pj_status_t app_init()
{
    pjsua_transport_id transport_id = -1;
    pjsua_transport_config tcp_cfg;
    unsigned i;
    pj_pool_t *tmp_pool;
    pj_status_t status;

    status = pjsua_create();
    if (status != PJ_SUCCESS)
        return status;
    
    /* Create pool for application */
    app_config.pool = pjsua_pool_create("pjsua-app", 1000, 1000);
    tmp_pool = pjsua_pool_create("tmp-pjsua", 1000, 1000);
    
    //初始化默认配置
    default_config();
    
    /* Initialize application callbacks */
    app_config.cfg.cb.on_call_state = &on_call_state;
    app_config.cfg.cb.on_call_media_state = &on_call_media_state;
    app_config.cfg.cb.on_incoming_call = &on_incoming_call;
    app_config.cfg.cb.on_reg_state2 = &on_reg_state2;
    app_config.cfg.cb.on_reg_state3 = &on_reg_state3;
//    app_config.cfg.cb.on_pager = &on_pager;
    app_config.cfg.cb.on_pager2 = &on_pager2;
    app_config.cfg.cb.on_pager_status2 = &on_pager_status2;
    app_config.cfg.cb.on_typing = &on_typing;
    app_config.cfg.cb.on_call_media_event = &on_call_media_event;
    
    /* Set sound device latency */
    if (app_config.capture_lat > 0)
        app_config.media_cfg.snd_rec_latency = app_config.capture_lat;
    if (app_config.playback_lat)
        app_config.media_cfg.snd_play_latency = app_config.playback_lat;
    
    /* Initialize pjsua */
    status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
                        &app_config.media_cfg);
    if (status != PJ_SUCCESS)
    {
        pj_pool_release(tmp_pool);
        return status;
    }
    
    pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
    
    /* Create ringback tones */
    if (app_config.no_tones == PJ_FALSE)
    {
        unsigned samples_per_frame;
        pjmedia_tone_desc tone[RINGBACK_CNT];
        pj_str_t name;
        
        samples_per_frame = app_config.media_cfg.audio_frame_ptime *
        app_config.media_cfg.clock_rate *
        app_config.media_cfg.channel_count / 1000;
        
        /* Ringback tone (call is ringing) */
        name = pj_str("ringback");
        status = pjmedia_tonegen_create2(app_config.pool, &name,
                                         app_config.media_cfg.clock_rate,
                                         app_config.media_cfg.channel_count,
                                         samples_per_frame,
                                         16, PJMEDIA_TONEGEN_LOOP,
                                         &app_config.ringback_port);
        if (status != PJ_SUCCESS)
            goto on_error;
        
        pj_bzero(&tone, sizeof(tone));
        for (i=0; i<RINGBACK_CNT; ++i) {
            tone[i].freq1 = RINGBACK_FREQ1;
            tone[i].freq2 = RINGBACK_FREQ2;
            tone[i].on_msec = RINGBACK_ON;
            tone[i].off_msec = RINGBACK_OFF;
        }
        tone[RINGBACK_CNT-1].off_msec = RINGBACK_INTERVAL;
        
        pjmedia_tonegen_play(app_config.ringback_port, RINGBACK_CNT, tone, PJMEDIA_TONEGEN_LOOP);
        
        
        status = pjsua_conf_add_port(app_config.pool, app_config.ringback_port, &app_config.ringback_slot);
        if (status != PJ_SUCCESS)
            goto on_error;
    }
    
    /* Add UDP transport unless it's disabled. */
    if (!app_config.no_udp) {
        pjsua_acc_id aid;
        pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP;
        
        status = pjsua_transport_create(type,
                                        &app_config.udp_cfg,
                                        &transport_id);
        if (status != PJ_SUCCESS)
            goto on_error;
        
        /* Add local account */
        pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
        
        /* Adjust local account config based on pjsua app config */
        {
            pjsua_acc_config acc_cfg;
            pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
            
            app_config_init_video(&acc_cfg);
            acc_cfg.rtp_cfg = app_config.rtp_cfg;
            pjsua_acc_modify(aid, &acc_cfg);
        }
        
        //pjsua_acc_set_transport(aid, transport_id);
        pjsua_acc_set_online_status(current_acc, PJ_TRUE);
        
        if (app_config.udp_cfg.port == 0) {
            pjsua_transport_info ti;
            pj_sockaddr_in *a;
            
            pjsua_transport_get_info(transport_id, &ti);
            a = (pj_sockaddr_in*)&ti.local_addr;
            
            tcp_cfg.port = pj_ntohs(a->sin_port);
        }
    }
    
    /* Add UDP IPv6 transport unless it's disabled. */
    if (!app_config.no_udp && app_config.ipv6) {
        pjsua_acc_id aid;
        pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP6;
        pjsua_transport_config udp_cfg;
        
        udp_cfg = app_config.udp_cfg;
        if (udp_cfg.port == 0)
            udp_cfg.port = 5060;
        else
            udp_cfg.port += 10;
        status = pjsua_transport_create(type, &udp_cfg, &transport_id);
        if (status != PJ_SUCCESS)
            goto on_error;
        
        /* Add local account */
        pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
        
        /* Adjust local account config based on pjsua app config */
        {
            pjsua_acc_config acc_cfg;
            pjsua_acc_get_config(aid, tmp_pool, &acc_cfg);
            
            app_config_init_video(&acc_cfg);
            acc_cfg.rtp_cfg = app_config.rtp_cfg;
            acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED;
            pjsua_acc_modify(aid, &acc_cfg);
        }
        
        //pjsua_acc_set_transport(aid, transport_id);
        pjsua_acc_set_online_status(current_acc, PJ_TRUE);
        
        if (app_config.udp_cfg.port == 0) {
            pjsua_transport_info ti;
            pj_sockaddr_in *a;
            
            pjsua_transport_get_info(transport_id, &ti);
            a = (pj_sockaddr_in*)&ti.local_addr;
            
            tcp_cfg.port = pj_ntohs(a->sin_port);
        }
    }
    
    if (transport_id == -1) {
        PJ_LOG(1,(THIS_FILE, "Error: no transport is configured"));
        status = -1;
        goto on_error;
    }
    
    /* Init call setting */
    pjsua_call_setting_default(&call_opt);
    call_opt.aud_cnt = app_config.aud_cnt;
    call_opt.vid_cnt = app_config.vid.vid_cnt;
    
    //启动PJSUA
    status = pjsua_start();
    if (status != PJ_SUCCESS)
        goto on_error;

    pj_pool_release(tmp_pool);
    return PJ_SUCCESS;
    
on_error:
    pj_pool_release(tmp_pool);
    app_destroy();
    return status;
}

static void im_type_status(pj_str_t uri, pj_bool_t typing)
{
    if(typing)
    {
        pjsua_im_typing(current_acc, &uri, PJ_TRUE, NULL);//正在输入
    }
    else
    {
        pjsua_im_typing(current_acc, &uri, PJ_FALSE, NULL);//停止输入
    }
}

static pj_status_t makeCall(pj_str_t uri, pj_bool_t videocall, pj_str_t call_id)
{
    pj_status_t status = -1;
    pjsua_call_setting_default(&call_opt);
    call_opt.aud_cnt = app_config.aud_cnt;
    if(YES == videocall)
    {
        //拨打视频电话
        call_opt.vid_cnt = app_config.vid.vid_cnt;
    }
    else
    {
        //拨打语音电话
        call_opt.vid_cnt = 0;
    }
    pj_str_t call_info = pj_str("call_info"); //发送INVITE的时候SIP头信息里的CALL_INFO
//    pj_str_t call_id;
    status = pjsua_call_make_call(current_acc, &uri, &call_opt, NULL, NULL, &current_call,&call_info,&call_id);
    PJ_LOG(3,(THIS_FILE," --------MakeCall--------call_id=%.*s--------",call_id.slen,call_id.ptr));
    
    return status;
}


static void answercall(int code)
{
    pjsua_call_info call_info = {0};
    pjsua_msg_data msg_data_ = {0};
    
    if((100 > code)&&(699 < code))
        return;
    
    if (current_call != -1)
    {
        pjsua_call_get_info(current_call, &call_info);
    }
    else
    {
        call_info.role = PJSIP_ROLE_UAC;
        call_info.state = PJSIP_INV_STATE_DISCONNECTED;
    }
    
    if (current_call == -1 || call_info.role != PJSIP_ROLE_UAS || call_info.state >= PJSIP_INV_STATE_CONNECTING)
    {
        puts("No pending incoming call");
        fflush(stdout);
        return;
    }
    else
    {
        char contact[120] = {0};
        pj_str_t hname = { "Contact", 7 };
        pj_str_t hvalue;
        pjsip_generic_string_hdr hcontact;
        
        pjsua_msg_data_init(&msg_data_);
        if (code/100 == 3)
        {
            hvalue = pj_str(contact);
            pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
            pj_list_push_back(&msg_data_.hdr_list, &hcontact);
        }
        
        if (current_call == -1) {
            return;
        }
        
        pjsua_call_answer2(current_call, &call_opt, code, NULL, &msg_data_);
    }
    
}


/*
 * arrange windows. arg:
 *   -1:    arrange all windows
 *   != -1: arrange only this window id
 */
void arrange_window(pjsua_vid_win_id wid)
{
    int i, last;
//    pjmedia_coord pos;
//    pos.x = 0;
//    pos.y = 10;
    
    [[IMService sharedInstance] IM_GetWinList];
//    if (wid != PJSUA_INVALID_ID)
//        pjsua_vid_win_set_pos(wid, &pos);
    
    i = (wid == PJSUA_INVALID_ID) ? 0 : wid;
    last = (wid == PJSUA_INVALID_ID) ? PJSUA_MAX_VID_WINS : wid+1;
    
    for (;i < last; ++i)
    {
        pjsua_vid_win_info wi;
        
        if (pjsua_vid_win_get_info(i, &wi) == PJ_SUCCESS)
        {
            UIView *view = (__bridge UIView *)wi.hwnd.info.ios.window;
            
            if (view)
            {
                if (!wi.is_native)
                {
                    [[IMService sharedInstance] IM_RemoteViewWithview:view];
                }
                else
                {
                    [[IMService sharedInstance] IM_LocaleViewWithview:view wid:wid];
                }
            }
        }
    }
}


/******************************************************  外部调用内部接口  ***************************************************************************/

#pragma mark - 外部调用内部接口 -
/**
 * PJSIP初始化
 */
-(BOOL)IM_InitPJSIP
{
    if(app_init())
        return YES;
    return NO;
}


/**
 * 远程服务器注册
 */
-(BOOL)IM_RegisterWithServer:(NSString *)server name:(NSString *)name password:(NSString *)pwd
{
    pjsua_acc_config acc_cfg = {0};
    pj_status_t status = -1;
    
    //判断服务器IP，用户名，密码是否为空
    if([server isEqualToString:@""]||[name isEqualToString:@"" ]||[pwd isEqualToString:@"" ])
    {
        return NO;
    }

    //格式化格式字符串
    char* id = (char *)[[NSString stringWithFormat:@"sip:%@@%@",name,server] UTF8String];
    char* registrar = (char *)[[NSString stringWithFormat:@"sip:%@",server] UTF8String];
    char* pname = (char *)[name UTF8String];
    char* passwd = (char *)[pwd UTF8String];
    
    pjsua_acc_config_default(&acc_cfg);
    acc_cfg.id = pj_str(id);
    acc_cfg.identity = pj_str("FUCK");
//    acc_cfg.ka_interval = 2;
    acc_cfg.reg_uri = pj_str(registrar);
    acc_cfg.cred_count = 1;
    acc_cfg.cred_info[0].scheme = pj_str("Digest");
    acc_cfg.cred_info[0].realm = pj_str("*");
    acc_cfg.cred_info[0].username = pj_str(pname);
    acc_cfg.cred_info[0].data_type = 0;
    acc_cfg.cred_info[0].data = pj_str(passwd);
    
    acc_cfg.rtp_cfg = app_config.rtp_cfg;
    app_config_init_video(&acc_cfg);
    
    status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
    if (status != PJ_SUCCESS)
        return NO;
    return YES;
}


/**
 * 重新注册或者注销
 */
-(void)IM_ReRegistrer
{
    pjsua_acc_set_registration(current_acc, PJ_TRUE);
}


/**
 * 删除并退出当前账号
 */
-(BOOL)IM_DeleteCurAcc
{
    if(pjsua_acc_is_valid(current_acc))
    {
        pjsua_acc_del(current_acc);
        return YES;
    }
    PJ_LOG(3,(THIS_FILE, "Invalid acc id %d",current_acc));
    return NO;
}


/**
 * 发送IM消息时状态
 * 对方正在输入
 */
-(void)IM_StartTypingWithuri:(NSString *)uri
{
    if([uri isEqualToString:@""])
        return;
    pj_str_t tmp_uri = pj_str((char *)[uri UTF8String]);
    im_type_status(tmp_uri,PJ_TRUE);
}


/**
 * 发送IM消息时状态
 * 停止输入
 */
-(void)IM_StopTypingWithuri:(NSString *)uri
{
    if([uri isEqualToString:@""])
        return;
    pj_str_t tmp_uri = pj_str((char *)[uri UTF8String]);
    im_type_status(tmp_uri,PJ_FALSE);
}


/**
 * 发送IM消息
 */
-(BOOL)IM_SendMSGWithuri:(NSString *)uri message:(NSString *)message
{
    if([uri isEqualToString:@""] || [ message isEqualToString:@""])
        return NO;
    
    pj_str_t pj_uri = pj_str((char *)[uri UTF8String]);
    pj_str_t pj_message = pj_str((char *)[message UTF8String]);
    pj_str_t pj_call_id;
    pj_status_t status = pjsua_im_send(current_acc, &pj_uri, NULL, &pj_message, &pj_call_id, NULL, NULL);
    if(PJ_SUCCESS != status)
        return NO;
    
    PJ_LOG(3,(THIS_FILE,"---------IM_SEND----------Call-id=%.*s---------",pj_call_id.slen, pj_call_id.ptr));
    return YES;
}


/**
 * 拨打视频电话
 */
-(BOOL)IM_MakeVideoWithuri:(NSString *)uri
{
    if([uri isEqualToString:@""])
        return NO;
    pj_str_t call_id = {0};
    pj_str_t tmp = pj_str((char *)[uri UTF8String]);
    pj_status_t status = makeCall(tmp, PJ_TRUE, call_id);
    if(PJ_SUCCESS != status)
        return NO;
    return YES;
}

/**
 * 拨打音频电话
 */
-(BOOL)IM_MakeAudioWithuri:(NSString *)uri
{
    if([uri isEqualToString:@""])
        return NO;
    pj_str_t call_id = {0};
    pj_str_t tmp = pj_str((char *)[uri UTF8String]);
    pj_status_t status = makeCall(tmp, PJ_FALSE, call_id);
    if(PJ_SUCCESS != status)
        return NO;
    return YES;
}

/**
 * 接听电话
 */
-(void)IM_AnswerCall
{
    answercall(200);
}

/**
 * 挂断电话
 */
-(void)IM_HangUp
{
    if (current_call != -1)
    {
        pjsua_call_hangup(current_call, 0, NULL, NULL);
    }
}

/**
 * 开启本地预览
 */
-(void)IM_PreViewOnWithid:(int)dev_id
{
    pj_bool_t has_native = pjsua_vid_preview_has_native(dev_id);
    if (has_native == PJ_TRUE)
    {
        pjsua_vid_preview_param param;
        pjsua_vid_preview_param_default(&param);
        param.wnd_flags = PJMEDIA_VID_DEV_WND_BORDER | PJMEDIA_VID_DEV_WND_RESIZABLE;
        pjsua_vid_preview_start(dev_id, &param);
        arrange_window(pjsua_vid_preview_get_win(dev_id));
    }
}

/**
 * 关闭本地预览
 */
-(void)IM_PreViewOffWithid:(int)dev_id
{
    pjsua_vid_win_id wid;
    wid = pjsua_vid_preview_get_win(dev_id);
    if (wid != PJSUA_INVALID_ID)
    {
        /* Preview window hiding once it is stopped is
         * responsibility of app */
        pjsua_vid_win_set_show(wid, PJ_FALSE);
        pjsua_vid_preview_stop(dev_id);
    }
}

/**
 * 获取所有编码信息
 */
-(void)IM_GetAllCodecInfo
{
    pjsua_codec_info c[32];
    int i;
    unsigned count = PJ_ARRAY_SIZE(c);
    
    printf("List of audio codecs:\n");
    pjsua_enum_codecs(c, &count);
    for (i=0; i<count; ++i)
    {
        printf("  %d\t%.*s\n", c[i].priority, (int)c[i].codec_id.slen, c[i].codec_id.ptr);
    }
    
    printf("\nList of video codecs:\n");
    pjsua_vid_enum_codecs(c, &count);
    for (i=0; i<count; ++i)
    {
        printf("  %d\t%.*s%s%.*s\n", c[i].priority,
               (int)c[i].codec_id.slen,
               c[i].codec_id.ptr,
               c[i].desc.slen? " - ":"",
               (int)c[i].desc.slen,
               c[i].desc.ptr);
    }
}

/**
 * 设置编码优先级
 * 优先级在0-255之间
 */
-(void)IM_SetCodecPriorityWithcodec:(NSString *)codec prio:(int)prio
{
    pj_str_t id;
    pj_status_t status;
    if([codec isEqualToString:@""])
        return;

    if (prio < 0)
        prio = 0;
    else if (prio > PJMEDIA_CODEC_PRIO_HIGHEST)
        prio = PJMEDIA_CODEC_PRIO_HIGHEST;
    
    const char *codesChar = [codec UTF8String];
    status = pjsua_codec_set_priority(pj_cstr(&id, codesChar),(pj_uint8_t)prio);
    if (status != PJ_SUCCESS)
    {
        status = pjsua_vid_codec_set_priority(pj_cstr(&id, codesChar),(pj_uint8_t)prio);
    }
    if (status != PJ_SUCCESS)
        pjsua_perror(THIS_FILE, "Error setting codec priority", status);
}

/**
 * 设置视屏帧数
 */
-(BOOL)IM_SetCodecFPSWithcodec:(NSString *)codec NUM:(int)NUM DEN:(int)DEN
{
    pjmedia_vid_codec_param cp;
    pj_str_t cid;
    pj_status_t status;
    if ([codec isEqualToString:@""]) {
        return NO;
    }
    cid = pj_str((char*)[codec UTF8String]);
    status = pjsua_vid_codec_get_param(&cid, &cp);
    if (status == PJ_SUCCESS)
    {
        cp.enc_fmt.det.vid.fps.num = NUM;
        cp.enc_fmt.det.vid.fps.denum = DEN;
        status = pjsua_vid_codec_set_param(&cid, &cp);
    }
    if (status != PJ_SUCCESS)
    {
        PJ_PERROR(1,(THIS_FILE, status, "Set codec framerate error"));
        return NO;
    }
    return YES;
}

/**
 * 设置视屏位宽
 */
-(BOOL)IM_SetCodecBWWithcodec:(NSString *)codec AVG:(int)AVG MAX:(int)MAX
{
    pjmedia_vid_codec_param cp;
    pj_str_t cid;
    pj_status_t status;
    
    if ([codec isEqualToString:@""]) {
        return NO;
    }
    
    cid = pj_str((char *)[codec UTF8String]);
    status = pjsua_vid_codec_get_param(&cid, &cp);
    if (status == PJ_SUCCESS)
    {
        cp.enc_fmt.det.vid.avg_bps = AVG * 1000;
        cp.enc_fmt.det.vid.max_bps = MAX * 1000;
        status = pjsua_vid_codec_set_param(&cid, &cp);
    }
    if (status != PJ_SUCCESS)
    {
        PJ_PERROR(1,(THIS_FILE, status, "Set codec bitrate error"));
        return NO;
    }
    
    return YES;
}


/**
 * 设置视屏编码宽高
 */
-(BOOL)IM_SetCodecSizeWithcodec:(NSString *)codec Weight:(int)Weight Hight:(int)Hight
{
    pjmedia_vid_codec_param cp;
    pj_str_t cid;
    pj_status_t status;
    
    if ([codec isEqualToString:@""]) {
        return NO;
    }
    cid = pj_str((char *)[codec UTF8String]);
    status = pjsua_vid_codec_get_param(&cid, &cp);
    if (status == PJ_SUCCESS)
    {
        cp.enc_fmt.det.vid.size.w = Weight;
        cp.enc_fmt.det.vid.size.h = Hight;
        status = pjsua_vid_codec_set_param(&cid, &cp);
    }
    if (status != PJ_SUCCESS)
    {
        PJ_PERROR(1,(THIS_FILE, status, "Set codec size error"));
        return NO;
    }
    
    return YES;
}


/**
 * 获取视屏编码信息
 */
-(void)IM_GetVideoCodecList
{
    pjsua_codec_info ci[PJMEDIA_CODEC_MGR_MAX_CODECS];
    unsigned count = PJ_ARRAY_SIZE(ci);
    pj_status_t status;
    status = pjsua_vid_enum_codecs(ci, &count);
    if (status != PJ_SUCCESS)
    {
        PJ_PERROR(1,(THIS_FILE, status, "Error enumerating codecs"));
    }
    else
    {
        unsigned i;
        PJ_LOG(3,(THIS_FILE, "Found %d video codecs:", count));
        PJ_LOG(3,(THIS_FILE, "codec id      prio  fps    bw(kbps)   size"));
        PJ_LOG(3,(THIS_FILE, "------------------------------------------"));
        for (i=0; i<count; ++i) {
            pjmedia_vid_codec_param cp;
            pjmedia_video_format_detail *vfd;
            
            status = pjsua_vid_codec_get_param(&ci[i].codec_id, &cp);
            if (status != PJ_SUCCESS)
                continue;
            
            vfd = pjmedia_format_get_video_format_detail(&cp.enc_fmt,
                                                         PJ_TRUE);
            PJ_LOG(3,(THIS_FILE, "%.*s%.*s %3d %7.2f  %4d/%4d  %dx%d",
                      (int)ci[i].codec_id.slen, ci[i].codec_id.ptr,
                      13-(int)ci[i].codec_id.slen, "                ",
                      ci[i].priority,
                      (vfd->fps.num*1.0/vfd->fps.denum),
                      vfd->avg_bps/1000, vfd->max_bps/1000,
                      vfd->size.w, vfd->size.h));
        }
    }
}

/**
 * 获取窗口信息
 */
-(void)IM_GetWinList
{
    pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS];
    unsigned i, cnt = PJ_ARRAY_SIZE(wids);
    
    pjsua_vid_enum_wins(wids, &cnt);
    PJ_LOG(2,(THIS_FILE, "Found %d video windows:", cnt));
    PJ_LOG(2,(THIS_FILE, "WID show    pos       size"));
    PJ_LOG(2,(THIS_FILE, "------------winlist------------"));
    for (i = 0; i < cnt; ++i)
    {
        pjsua_vid_win_info wi;
        pjsua_vid_win_get_info(wids[i], &wi);
        PJ_LOG(2,(THIS_FILE, "%3d   %c  (%d,%d)  %dx%d",
                  wids[i], (wi.show?'Y':'N'), wi.pos.x, wi.pos.y,
                  wi.size.w, wi.size.h));
    }
}

/**
 * 切换捕获设备
 */
-(void)IM_SwitchCaptureWithCapId:(int)CapId
{
    pjsua_call_vid_strm_op_param param;
    pj_status_t status = PJ_SUCCESS;
    
    pjsua_call_vid_strm_op_param_default(&param);
    param.med_idx =  -1;
    param.cap_dev = CapId;
    status = pjsua_call_set_vid_strm(current_call, PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV, &param);
    
    if (status != PJ_SUCCESS)
    {
        PJ_PERROR(1,(THIS_FILE, status, "Error modifying video stream"));
    }
}

/**
 * 获取捕获设备
 */
-(void)IM_ShowAllDevList
{
    unsigned i, count;
    pjmedia_vid_dev_info vdi;
    pj_status_t status;
    
    PJ_LOG(3,(THIS_FILE, "Video device list:"));
    count = pjsua_vid_dev_count();
    if (count == 0)
    {
        PJ_LOG(3,(THIS_FILE, " - no device detected -"));
        return;
    } else {
        PJ_LOG(3,(THIS_FILE, "%d device(s) detected:", count));
    }
    
    for (i=0; i<count; ++i) {
        status = pjsua_vid_dev_get_info(i, &vdi);
        if (status == PJ_SUCCESS)
        {
            opts[i].id = i;
            strcpy(opts[i].name, vdi.name);
            PJ_LOG(3,(THIS_FILE, "%3d %s [%s][%s]", i, vdi.name, vdi.driver, vdi.dir == PJMEDIA_DIR_CAPTURE?"capture":"render"));
        }
    }
}


/***********************************************    回调     *****************************************************/

#pragma mark - 回调 -
-(void)IM_LogInSuccess
{
    [[IMViewController shareInstance] recvNotifyWithstr:@"登录成功"];
}

-(void)IM_LogOutSuccess
{
    [[IMViewController shareInstance] recvNotifyWithstr:@"退出成功"];
}

-(void)IM_RingStart
{
    
}

-(void)IM_RingStop
{
    
}

-(void)IM_RecvVideoCallWithfrom:(const pj_str_t *)from to:(const pj_str_t *)to
{
    NSString *NSFrom = [NSString stringWithCString:from->ptr encoding:NSUTF8StringEncoding];
    NSString *NSTo = [NSString stringWithCString:to->ptr encoding:NSUTF8StringEncoding];
    [[IMViewController shareInstance] recvVideoCallWithfrom:NSFrom to:NSTo];
}

-(void)IM_RecvAudioCallWithfrom:(const pj_str_t *)from to:(const pj_str_t *)to
{
    NSString *NSFrom = [NSString stringWithCString:from->ptr encoding:NSUTF8StringEncoding];
    NSString *NSTo = [NSString stringWithCString:to->ptr encoding:NSUTF8StringEncoding];
    [[IMViewController shareInstance] recvAudioCallWithfrom:NSFrom to:NSTo];
}

-(void)IM_RecvMsgWithfrom:(const pj_str_t *)from to:(const pj_str_t *)to text:(const pj_str_t *)text
{
    NSString *NSText = [NSString stringWithCString:text->ptr encoding:NSUTF8StringEncoding];
    [[IMViewController shareInstance] recvMessageWithstr:NSText];
}

-(void)IM_RemoteIsTypingWithfrom:(const pj_str_t *)from to:(const pj_str_t *)to is_typing:(pj_bool_t)is_typing
{
    if(is_typing)
        [[IMViewController shareInstance] recvNotifyWithstr:@"对方正在输入"];
    else
        [[IMViewController shareInstance] recvNotifyWithstr:@""];
}

-(void)IM_InviteDisconnectWithcallid:(int)callid code:(int)code status:(const pj_str_t *)status
{
    NSString *NSStatus = [NSString stringWithCString:status->ptr encoding:NSUTF8StringEncoding];
    [[IMViewController shareInstance] InviteDisconnectWithcallid:callid code:code status:NSStatus];
}

-(void)IM_RemoteViewWithview:(UIView *)view
{
    [[IMViewController shareInstance] showRemoteViewWithView:view];
}

-(void)IM_LocaleViewWithview:(UIView *)view wid:(pjsua_vid_win_id)wid
{
    if(wid != PJSUA_INVALID_ID)
       [[IMViewController shareInstance] showlocaleViewWithView:view displayBigNative:YES];
    else
        [[IMViewController shareInstance] showlocaleViewWithView:view displayBigNative:NO];
}

@end
